package com.ideaaedi.commonds.img;


import com.google.common.collect.Lists;
import com.ideaaedi.commonds.constants.PatternConstant;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Base64;
import java.util.List;
import java.util.Objects;

/**
 * 图片工具类
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2100.4.6
 */
public class ImgUtil {
    
    /** 行间距(px) */
    public static final int DEFAULT_LINE_SPACING = 5;
    
    /** 左下角 */
    public static final int POSITION_BOTTOM_LEFT = 1;
    
    /** 右下角 */
    public static final int POSITION_BOTTOM_RIGHT = 2;
    
    /** 左上角 */
    public static final int POSITION_TOP_LEFT = 3;
    
    /** 右上角 */
    public static final int POSITION_TOP_RIGHT = 4;
    
    /**
     * 生成带有文字的透明背景的图片 <br/> 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @return 图片
     * @see ImgUtil#generateImgWithStr(List, int, int, Font, Color, int, int)
     */
    public static BufferedImage generateImgWithStr(List<String> contentLines, int imgWidth, int imgHeight) {
        return generateImgWithStr(contentLines, imgWidth, imgHeight, new Font("微软雅黑", Font.PLAIN, 0),
                new Color(255, 255, 255), DEFAULT_LINE_SPACING, POSITION_BOTTOM_LEFT);
    }
    
    /**
     * 生成带有文字的透明背景的图片 <br/> 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @return 图片
     * @see ImgUtil#generateImgWithStr(List, int, int, Font, Color, int, int)
     */
    public static BufferedImage generateImgWithStr(List<String> contentLines, int imgWidth, int imgHeight,
                                                   Font font, Color fontColor) {
        return generateImgWithStr(contentLines, imgWidth, imgHeight, font, fontColor, DEFAULT_LINE_SPACING,
                POSITION_BOTTOM_LEFT);
    }
    
    /**
     * 生成带有文字的透明背景的图片 <br/> 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @param contentLines
     *         文本
     * @param imgWidth
     *         图片宽
     * @param imgHeight
     *         图片高
     * @param font
     *         字
     * @param fontColor
     *         字体颜色
     * @param lineSpacing
     *         行间距(单位: 像素)
     * @param contentPosition
     *         文字位置 <br />
     *         {@link ImgUtil#POSITION_BOTTOM_LEFT}
     *         {@link ImgUtil#POSITION_BOTTOM_RIGHT}
     *         {@link ImgUtil#POSITION_TOP_LEFT}
     *         {@link ImgUtil#POSITION_TOP_RIGHT}
     *
     * @return 图片
     */
    public static BufferedImage generateImgWithStr(List<String> contentLines, int imgWidth, int imgHeight,
                                                   Font font, Color fontColor, int lineSpacing, int contentPosition) {
        if (imgWidth <= 0 || imgHeight <= 0) {
            throw new IllegalArgumentException("imgWidth or imgHeight cannot less than 0 or equal 0.");
        }
        
        // step1. 计算文本的行数以及最大长度
        Triple<Integer, Integer, Integer> triple = computeLineCountAndMaxLength(null, contentLines, font);
        int lineCount = triple.getLeft();
        int maxLength;
        
        // step2. 计算字体大小（当字体大小<=0时，将根据图片大小自动计算字体的大小）
        int fontSize = font.getSize();
        if (fontSizeIsIllegal(fontSize)) {
            //noinspection PointlessArithmeticExpression
            double computeHeight = imgHeight * 1 - (lineCount - 1) * lineSpacing;
            //noinspection PointlessArithmeticExpression
            double computeWidth = imgWidth * 1;
            if (computeHeight <= 0 || computeWidth <= 0) {
                throw new IllegalArgumentException("computeHeight or computeWidth is illegal.");
            }
            Integer maxLengthLineCharCount = triple.getRight();
            Objects.requireNonNull(maxLengthLineCharCount, "maxLengthLineCharCount should not be null while fontSize "
                    + "<= 0.");
            fontSize = (int) Math.min(computeHeight / lineCount, computeWidth / maxLengthLineCharCount);
            // 文本的最大长度(像素)
            maxLength = fontSize * maxLengthLineCharCount;
        } else {
            Integer middle = triple.getMiddle();
            Objects.requireNonNull(middle, "middle should not be null while fontSize > 0.");
            // 文本的最大长度(像素)
            maxLength = middle;
        }
        
        // step3. 创建 图形1(图层1) 作为 底色
        BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = bufferedImage.createGraphics();
        graphics.clearRect(0, 0, imgWidth, imgHeight);
        bufferedImage = graphics.getDeviceConfiguration().createCompatibleImage(imgWidth, imgHeight,
                Transparency.TRANSLUCENT);
        graphics.dispose();
        
        // step4. 创建 图形2(图层2) 勾画文本
        graphics = bufferedImage.createGraphics();
        graphics.setColor(fontColor);
        graphics.setFont(new Font(font.getName(), font.getStyle(), fontSize));
        // x向右为正, y向下为正
        int startX;
        //  这里发现：Y坐标， 还需要调整(fontSize / 6)后，文字才能正好显示
        int startY;
        switch (contentPosition) {
            case POSITION_BOTTOM_LEFT:
                startX = 0;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
                break;
            case POSITION_BOTTOM_RIGHT:
                startX = imgWidth - maxLength;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
                break;
            case POSITION_TOP_LEFT:
                startX = 0;
                startY = fontSize - (fontSize / 6);
                break;
            case POSITION_TOP_RIGHT:
                startX = imgWidth - maxLength;
                startY = fontSize - (fontSize / 6);
                break;
            default:
                // 默认位于左下角
                startX = 0;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
        }
        for (int i = 0; i < contentLines.size(); i++) {
            graphics.drawString(contentLines.get(i), startX, startY + i * (fontSize + lineSpacing));
        }
        graphics.dispose();
        
        return bufferedImage;
    }
    
    /**
     * 将文字转换为透明背景的图片
     *
     * @param contentLines
     *         文本
     * @param fontSize
     *         字体大小(像素)
     *
     * @return 图片
     * @see ImgUtil#strConvertToImg(List, Font, Color, int)
     */
    public static BufferedImage strConvertToImg(List<String> contentLines, int fontSize) {
        return strConvertToImg(contentLines, new Font("微软雅黑", Font.PLAIN, fontSize),
                new Color(255, 255, 255), DEFAULT_LINE_SPACING);
    }
    
    /**
     * 将文字转换为透明背景的图片
     *
     * @return 图片
     * @see ImgUtil#strConvertToImg(List, Font, Color, int)
     */
    public static BufferedImage strConvertToImg(List<String> contentLines, Font font, Color fontColor) {
        return strConvertToImg(contentLines, font, fontColor, DEFAULT_LINE_SPACING);
    }
    
    /**
     * 将文字转换为透明背景的图片
     *
     * @param contentLines
     *         文本
     * @param font
     *         字
     * @param fontColor
     *         字体颜色
     * @param lineSpacing
     *         行间距(单位: 像素)
     *
     * @return 图片
     */
    public static BufferedImage strConvertToImg(List<String> contentLines, Font font, Color fontColor,
                                                int lineSpacing) {
        int fontSize = font.getSize();
        if (fontSizeIsIllegal(fontSize)) {
            throw new IllegalArgumentException("fontSize cannot less than 0 or equal 0.");
        }
        
        // step1. 计算文本的行数以及最大长度
        Triple<Integer, Integer, Integer> triple = computeLineCountAndMaxLength(null, contentLines, font);
        int lineCount = triple.getLeft();
        Integer middle = triple.getMiddle();
        Objects.requireNonNull(middle, "middle should not be null while fontSize > 0.");
        int width = middle;
        
        // step2. 根据字体大小，计算高度
        int height = fontSize * lineCount + (lineCount - 1) * lineSpacing;
        
        // step3.  创建 图形1(图层1) 作为 底色
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = bufferedImage.createGraphics();
        graphics.clearRect(0, 0, width, height);
        bufferedImage = graphics.getDeviceConfiguration().createCompatibleImage(width, height,
                Transparency.TRANSLUCENT);
        graphics.dispose();
        
        // step4. 创建 图形2(图层2) 勾画文本
        graphics = bufferedImage.createGraphics();
        graphics.setColor(fontColor);
        graphics.setFont(font);
        // x向右为正, y向下为正
        int startX = 0;
        //  这里发现：还需要 调整(fontSize / 6)后，文字才能正好显示
        int startY = height - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
        for (int i = 0; i < contentLines.size(); i++) {
            graphics.drawString(contentLines.get(i), startX, startY + i * (fontSize + lineSpacing));
        }
        graphics.dispose();
        return bufferedImage;
    }
    
    /**
     * 给图片添加水印
     * <br/>
     * 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @return 图片（与入参bufferedImage是同一个对象）
     * @see ImgUtil#addWatermark(BufferedImage, List, Font, Color, int, int)
     */
    public static BufferedImage addWatermark(BufferedImage bufferedImage, List<String> contentLines) {
        return addWatermark(bufferedImage, contentLines, new Font("微软雅黑", Font.PLAIN, 0), new Color(255, 255, 255), DEFAULT_LINE_SPACING, POSITION_BOTTOM_LEFT);
    }
    
    /**
     * 给图片添加水印
     * <br/>
     * 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @return 图片（与入参bufferedImage是同一个对象）
     * @see ImgUtil#addWatermark(BufferedImage, List, Font, Color, int, int)
     */
    public static BufferedImage addWatermark(BufferedImage bufferedImage, List<String> contentLines, Font font, Color fontColor) {
        return addWatermark(bufferedImage, contentLines, font, fontColor, DEFAULT_LINE_SPACING, POSITION_BOTTOM_LEFT);
    }
    
    /**
     * 给图片添加水印
     * <br/>
     * 注：若参数font中设置的字体大小<=0，则会自动根据图片设置字体大小
     *
     * @param bufferedImage
     *         图片
     * @param contentLines
     *         文本
     * @param font
     *         字
     * @param fontColor
     *         字体颜色
     * @param lineSpacing
     *         行间距(单位: 像素)
     * @param contentPosition
     *         文字位置 <br />
     *         {@link ImgUtil#POSITION_BOTTOM_LEFT}
     *         {@link ImgUtil#POSITION_BOTTOM_RIGHT}
     *         {@link ImgUtil#POSITION_TOP_LEFT}
     *         {@link ImgUtil#POSITION_TOP_RIGHT}
     *
     * @return 图片（与入参bufferedImage是同一个对象）
     */
    public static BufferedImage addWatermark(BufferedImage bufferedImage, List<String> contentLines,
                                             Font font, Color fontColor, int lineSpacing, int contentPosition) {
        if (bufferedImage == null) {
            throw new IllegalArgumentException("bufferedImage cannot be null.");
        }
        // step1. 计算文本的行数以及最大长度
        Triple<Integer, Integer, Integer> triple = computeLineCountAndMaxLength(bufferedImage, contentLines, font);
        int lineCount = triple.getLeft();
        int maxLength;
    
    
        int imgHeight = bufferedImage.getHeight();
        int imgWidth = bufferedImage.getWidth();
        // step2. 计算字体大小（当字体大小<=0时，将根据图片大小自动计算字体的大小）
        int fontSize = font.getSize();
        if (fontSizeIsIllegal(fontSize)) {
            //noinspection PointlessArithmeticExpression
            double computeHeight = imgHeight * 1 - (lineCount - 1) * lineSpacing;
            //noinspection PointlessArithmeticExpression
            double computeWidth = imgWidth * 1;
            if (computeHeight <= 0 || computeWidth <= 0) {
                throw new IllegalArgumentException("computeHeight or computeWidth is illegal.");
            }
            Integer maxLengthLineCharCount = triple.getRight();
            Objects.requireNonNull(maxLengthLineCharCount, "maxLengthLineCharCount should not be null while fontSize "
                    + "<= 0.");
            fontSize = (int) Math.min(computeHeight / lineCount, computeWidth / maxLengthLineCharCount);
            // 文本的最大长度(像素)
            maxLength = fontSize * maxLengthLineCharCount;
        } else {
            Integer middle = triple.getMiddle();
            Objects.requireNonNull(middle, "middle should not be null while fontSize > 0.");
            // 文本的最大长度(像素)
            maxLength = middle;
        }
        // step3. 在原图的基础上创建图层，用来勾画文本
        Graphics2D graphics = bufferedImage.createGraphics();
        graphics.setColor(fontColor);
        graphics.setFont(new Font(font.getName(), font.getStyle(), fontSize));
        // x向右为正, y向下为正
        int startX;
        //  这里发现：Y坐标， 还需要调整(fontSize / 6)后，文字才能正好显示
        int startY;
        switch (contentPosition) {
            case POSITION_BOTTOM_LEFT:
                startX = 0;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
                break;
            case POSITION_BOTTOM_RIGHT:
                startX = imgWidth - maxLength;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
                break;
            case POSITION_TOP_LEFT:
                startX = 0;
                startY = fontSize - (fontSize / 6);
                break;
            case POSITION_TOP_RIGHT:
                startX = imgWidth - maxLength;
                startY = fontSize - (fontSize / 6);
                break;
            default:
                // 默认位于左下角
                startX = 0;
                startY = imgHeight - fontSize * lineCount - lineSpacing * (lineCount - 1) + fontSize - (fontSize / 6);
        }
        for (int i = 0; i < contentLines.size(); i++) {
            graphics.drawString(contentLines.get(i), startX, startY + i * (fontSize + lineSpacing));
        }
        graphics.dispose();
        return bufferedImage;
    }
    
    /**
     * 计算文本的行数 & 最大长度(像素)  & 预估最长行的中文字符个数
     *
     * @param bufferedImage
     *         图片上下文
     * @param contentLines
     *         （多行）文本
     * @param font
     *         字体
     *
     * @return <ul>
     * <li>左 - 行数
     * <li>中 - 最长行长度(像素)</li>
     * <li>右 - 预估的最长行的中文字符个数(其它字符按中文字符长度进行了个数换算)</li>
     * </ul>
     */
    public static Triple<Integer, Integer, Integer> computeLineCountAndMaxLength(@Nullable BufferedImage bufferedImage,
                                                                                 List<String> contentLines, Font font) {
        if (contentLines == null || contentLines.size() == 0) {
            throw new IllegalArgumentException("contentLines cannot be null.");
        }
        int fontSize = font.getSize();
        Integer maxLength = null;
        Integer maxLengthLineCharCount = null;
        if (fontSize > 0) {
            if (bufferedImage == null) {
                bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
            }
            Graphics2D graphics = bufferedImage.createGraphics();
            FontMetrics fontMetrics = graphics.getFontMetrics(font);
            // 最长行的长度(像素)
            maxLength = contentLines.stream()
                    .map(fontMetrics::stringWidth)
                    .max(Double::compare).orElse(0);
            if (maxLength == 0) {
                throw new IllegalArgumentException("contentLines is illegal. maxLength less than 0. contentLines -> " + contentLines);
            }
        } else {
            // 最长行的长度(计算式，按：中文字符标点的长度为1， 其余的长度为0.5进行计算)
            double maxLengthDouble = contentLines.stream()
                    .map(str -> {
                        double length = 0;
                        String[] charStr = str.split("");
                        for (String c : charStr) {
                            if (PatternConstant.CHINESE_PATTERN.matcher(c).matches()) {
                                // 中文文字、中文符号，算2/2个长度
                                length = length + 2;
                            } else {
                                // 其余的算1.1/2个长度
                                length = length + 1.1;
                            }
                        }
                        return length;
                    })
                    .max(Double::compare).orElse(0d);
            maxLengthLineCharCount = (int) ((maxLengthDouble + 1) / 2);
        }
        return Triple.of(contentLines.size(), maxLength, maxLengthLineCharCount);
    }
    
    /**
     * 解析Base64图片
     *
     * @return 左-图片字节；右-图片后缀名
     */
    public static Pair<byte[], String> parseBase64Img(String base64Data) {
        Objects.requireNonNull(base64Data, "base64Data cannot be null.");
        // 清理Base64数据
        String imageData = pureBase64ImgData(base64Data);
        // 图片字节
        byte[] imageBytes = Base64.getMimeDecoder().decode(imageData);
        // 图片后缀名
        String imgSuffix = obtainBase64ImgSuffix(base64Data);
        return Pair.of(imageBytes, imgSuffix);
    }
    
    /**
     * 纯化base64数据，移除可能存在的相关前缀
     * <pre>
     *  纯化前：
     *  纯化后：xxxxxx
     * </pre>
     *
     * @param imgBase64 图片base64数据
     * 
     * @return 纯粹的base64数据
     */
    public static String pureBase64ImgData(String imgBase64) {
        int idx = imgBase64.indexOf(",");
        if (idx < 0) {
            return imgBase64;
        }
        return imgBase64.substring(idx + 1);
    }
    
    /**
     * 获取图片后缀名
     *
     * @param imgBase64 图片base64数据
     *
     * @return 图片后缀名（如：.jpg）
     */
    public static String obtainBase64ImgSuffix(String imgBase64) {
        if (imgBase64.startsWith("data:image/jpeg")) {
            return ".jpg";
        }
        if (imgBase64.startsWith("data:image/png")) {
            return ".png";
        }
        if (imgBase64.startsWith("data:image/gif")) {
            return ".gif";
        }
        if (imgBase64.startsWith("data:image/bmp")) {
            return ".bmp";
        }
        if (imgBase64.startsWith("data:image/webp")) {
            return "webp";
        }
        // 默认为png
        return ".png";
    }
    
    /**
     * 文字大小是否非法
     *
     * @param fontSize
     *         大小
     *
     * @return true-非法；false-合法
     */
    private static boolean fontSizeIsIllegal(int fontSize) {
        return fontSize <= 0;
    }
    
    /**
     * demo
     */
    public static void main(String[] args) throws Exception {
        BufferedImage bufferedImage = strConvertToImg(
                Lists.newArrayList(
                        "服务对象：叶桃花",
                        "服务内容：理疗",
                        "服务时长：1小时20分钟",
                        "服务人员：张三",
                        "提交时间：2022-08-08 12:13:14.123456789101112131415",
                        "详细地址：成都市双流区新怡花园"
                ), new Font("幼圆", Font.PLAIN, 20),
                new Color(255, 0, 0));
        System.err.println(
                ImageIO.write(bufferedImage, "PNG", new File("E:\\360MoveData\\Users\\邓沙利文\\Desktop\\abc.jpg"))
        );
    }
    
}
